Sumário¶
1. Preparação dos dados
1.1 - Imports e carregamento de dados
1.2 - Informações básicas dos dados
1.3 - Info sobre Países
1.4 - Homogeneizar e corrigir ISO de países
1.5 - Limpeza de dados e Est. Descritivas
1.6 - Análise básica de trocas
2. AED
2.1 - Variações anuais
2.2 - Top Exportadores em $
2.3 - Top Exportadores em qty
2.4 - Modificações nos Tops
3. Construção das redes
3.1 - Rede Pré-Pandemia - Spring Layout
3.2 - Análise por Períodos
3.2.1 - Estatísticas básicas
3.2.2 - Medidas de Centralidade
3.2.3 - Propriedades de conectividade
3.2.4 - Medidas de distância
3.2.5 - Detecção de Comunidades
3.3 - Mapas Temporais
4. O Caso de Portugal
1. Preparação dos Dados¶
1.1 Imports e carregamento de dados¶
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import matplotlib.ticker as ticker
import random
import pycountry
import math
from IPython.display import display
import country_converter as coco
import warnings
warnings.filterwarnings('ignore')
import networkx as nx
import community
from networkx.algorithms import community as nx_community
import plotly.graph_objects as go
#settings de vis
plt.rcParams['figure.figsize'] = [10, 6]
sns.set_style("whitegrid")
plt.rcParams['font.size'] = 12
# carregar dados
df = pd.read_csv('dataset4.csv', sep=";", encoding='latin-1')
display(df.head())
| refYear | refMonth | flowDesc | reporterISO | partnerISO | qty | primaryValue | unitPrice | |
|---|---|---|---|---|---|---|---|---|
| 0 | 2015 | 1 | Import | DZA | W00 | 0 | 391716,54 | #DIV/0! |
| 1 | 2015 | 1 | Import | DZA | CAN | 0 | 466,05 | #DIV/0! |
| 2 | 2015 | 1 | Import | DZA | CHN | 0 | 154815 | #DIV/0! |
| 3 | 2015 | 1 | Import | DZA | CZE | 0 | 54,72 | #DIV/0! |
| 4 | 2015 | 1 | Import | DZA | FRA | 0 | 47176,61 | #DIV/0! |
1.2 Informações básicas dos dados¶
# colunas no df
print("Colunas no DataFrame:\n")
print(df.columns.tolist())
print("\n",100*"=")
# infos básicas do dataset
print(f"\nTotal de registos: {len(df)}\n")
print(f"Anos incluídos: {df['refYear'].unique()}")
print(f"Número de países Exportadores: {df['reporterISO'].nunique()}")
print(f"Número de países Importadores: {df['partnerISO'].nunique()}")
print("\n",100*"=")
print("\nTipos de dados:\n")
display(df.dtypes)
Colunas no DataFrame: ['refYear', 'refMonth', 'flowDesc', 'reporterISO', 'partnerISO', 'qty', 'primaryValue', 'unitPrice'] ==================================================================================================== Total de registos: 794265 Anos incluídos: [2015 2016 2017 2018 2019 2020 2021 2022 2023 2024] Número de países Exportadores: 156 Número de países Importadores: 247 ==================================================================================================== Tipos de dados:
refYear int64 refMonth int64 flowDesc object reporterISO object partnerISO object qty object primaryValue object unitPrice object dtype: object
1.3 Info sobre Países¶
print(f"Países Exportadores: {df['reporterISO'].unique()}\n")
print(f"\nPaíses Importadores: {df['partnerISO'].unique()}")
Países Exportadores: ['DZA' 'AND' 'AGO' 'ATG' 'AZE' 'ARG' 'AUS' 'AUT' 'BHR' 'ARM' 'BRB' 'BEL' 'BMU' 'BOL' 'BIH' 'BWA' 'BRA' 'BLZ' 'BRN' 'BGR' 'MMR' 'BDI' 'BLR' 'KHM' 'CAN' 'CPV' 'CHL' 'COL' 'COM' 'COG' 'COD' 'HRV' 'CYP' 'CZE' 'BEN' 'DNK' 'DOM' 'ECU' 'SLV' 'ETH' 'EST' 'FJI' 'FIN' 'FRA' 'PYF' 'GEO' 'DEU' 'GHA' 'GRC' 'GRL' 'GRD' 'GTM' 'GUY' 'HKG' 'HUN' 'ISL' 'IDN' 'IRL' 'ISR' 'ITA' 'CIV' 'JPN' 'KAZ' 'KOR' 'KWT' 'KGZ' 'LAO' 'LSO' 'LVA' 'LTU' 'LUX' 'MAC' 'MDG' 'MWI' 'MYS' 'MLT' 'MUS' 'MEX' 'MDA' 'MNE' 'MSR' 'MAR' 'MOZ' 'OMN' 'NLD' 'NCL' 'NZL' 'NIC' 'NOR' 'PLW' 'PAK' 'PAN' 'PRY' 'PER' 'PHL' 'POL' 'PRT' 'QAT' 'ROU' 'RUS' 'RWA' 'KNA' 'VCT' 'STP' 'SAU' 'SEN' 'SRB' 'SYC' 'IND' 'SGP' 'SVK' 'VNM' 'SVN' 'ZAF' 'ZWE' 'ESP' 'SDN' 'SWZ' 'SWE' 'CHE' 'THA' 'TTO' 'TUR' 'UGA' 'UKR' 'MKD' 'EGY' 'GBR' 'TZA' 'USA' 'BFA' 'URY' 'WSM' 'YEM' 'ZMB' 'TGO' 'GMB' 'SLB' 'CHN' 'HND' 'MNG' 'NER' 'SLE' 'PSE' 'KIR' 'JOR' 'KEN' 'NAM' 'ARE' 'CRI' 'MDV' 'NGA' 'ABW' 'UZB' 'MRT' 'TUN'] Países Importadores: ['W00' 'CAN' 'CHN' 'CZE' 'FRA' 'ITA' 'KOR' 'S19' 'PAK' 'POL' 'PRT' 'RUS' 'IND' 'ESP' 'SWE' 'TUN' 'TUR' 'EGY' 'GBR' 'USA' 'DEU' 'NLD' 'CHE' 'BEL' 'BRA' 'COD' 'DNK' 'ISR' 'JPN' 'LBN' 'MEX' 'MAR' 'NAM' 'SGP' 'ZAF' 'ARE' 'SXM' 'PER' 'AUS' 'IRN' 'LKA' 'COL' 'HKG' 'MYS' 'ROU' 'VNM' 'URY' 'AUT' 'BGD' 'BIH' 'KHM' 'FJI' 'FIN' 'HND' 'HUN' 'IDN' 'IRL' 'KEN' 'LVA' 'MDG' 'NPL' 'NZL' 'NOR' 'PHL' 'SVK' 'THA' 'UKR' 'MKD' 'BOL' 'HRV' 'EST' 'GMB' 'LUX' 'SRB' 'SVN' '_X ' 'CRI' 'GIN' 'JAM' 'PAN' 'MAF' 'SUR' 'CHL' 'SAU' 'ARG' 'GTM' 'BGR' 'ZMB' 'VGB' 'ECU' 'BRB' 'GRC' 'UGA' 'LTU' 'TZA' 'DMA' 'NIC' 'PRY' 'ATA' 'GHA' 'KWT' 'MLI' 'NCL' 'PNG' 'SEN' 'TON' 'X1 ' 'AZE' 'BHS' 'ARM' 'BLR' 'CMR' 'CYP' 'GAB' 'GEO' 'ISL' 'KAZ' 'LBR' 'LBY' 'MLT' 'MNE' 'OMN' 'QAT' 'SYR' 'X2 ' 'CYM' 'GRD' 'LCA' 'VCT' 'TTO' 'DZA' 'BEN' 'GNQ' 'ERI' 'PYF' 'GIB' 'IRQ' 'JOR' 'MUS' 'F19' 'SLE' 'AGO' 'CIV' 'MOZ' 'AFG' 'DOM' 'TKM' 'SLV' 'E19' 'SMR' 'LAO' 'MDA' 'NGA' 'ALB' 'BHR' 'BMU' 'CUB' 'CUW' 'UMI' 'SPM' 'BFA' 'HTI' 'VEN' 'MDV' 'FRO' 'GRL' 'KGZ' 'UZB' 'SLB' 'VUT' 'ATG' 'BVT' 'PRK' 'GUM' 'AND' 'CAF' 'TCD' 'COM' 'COG' 'DJI' 'MRT' 'ABW' 'RWA' 'BLM' 'SYC' 'ZWE' 'TGO' 'PSE' 'MAC' 'BRN' 'GUY' 'MNG' 'SDN' 'YEM' 'MMR' 'BLZ' 'ETH' 'BDI' 'WLF' 'COK' 'PLW' 'WSM' 'SOM' 'CPV' 'GNB' 'STP' 'LSO' 'XX ' 'BTN' 'MWI' 'NER' 'MNP' 'BWA' 'SWZ' 'TJK' 'MHL' 'KNA' 'TCA' 'KIR' 'BES' 'A79' 'NIU' 'TLS' 'AIA' 'NRU' 'TKL' 'O19' 'MSR' 'ASM' 'TUV' 'FSM' 'SSD' 'CXR' 'NFK' 'IOT' 'MYT' 'CCK' 'SHN' 'FLK' 'SGS' 'VAT' 'ATF' 'PCN' 'ESH' 'ATB' 'A59' 'HMD']
1.4 Homogeneizar e corrigir ISO de países¶
# Todo este bloco de código foi sugerido pelo chatGPT. Utilizei para, dada a lista completa de países,
# detectar redundâncias, erros ou nomes que não correspondem a países reais. Após fazer isso, sugeriu esta função para corrigir
# nomes. A função estava mais simples que a minha, optei usar esta.
def corrigir_nomes_paises(df):
df_corrigido = df.copy()
# Códigos que não existem
entidades_especiais = ['W00', 'S19', '_X ', 'X1 ', 'X2 ', 'F19', 'E19', 'A79', 'O19', 'A59', 'XX ']
# Filtrar apenas países reais (removendo todas as entidades especiais)
df_corrigido = df_corrigido[
(~df_corrigido['reporterISO'].isin(entidades_especiais)) &
(~df_corrigido['partnerISO'].isin(entidades_especiais))
]
return df_corrigido
df_corrigido = corrigir_nomes_paises(df)
# infos básicas do dataset
print(f"\nTotal de registos: {len(df_corrigido)}\n")
print(f"Anos incluídos: {df_corrigido['refYear'].unique()}")
print(f"Número de países Exportadores: {df_corrigido['reporterISO'].nunique()}")
print(f"Número de países Importadores: {df_corrigido['partnerISO'].nunique()}")
Total de registos: 748813 Anos incluídos: [2015 2016 2017 2018 2020 2021 2022 2023 2024] Número de países Exportadores: 155 Número de países Importadores: 236
Nota: Ao retirar o WOO como _"entidade_especial" o ano de 2019 é todo eliminado pois todas os importadores de 2019 estão listados como WOO em partnerISO
1.5 Limpeza de dados e Est. Descritivas¶
# limpar #N/A decorrente de qty = 0 no cálculo do unitPrice - está com #N/A porque fiz este cálculo no excel, antes de carregar os dados aqui
df_corrigido['qty'] = df_corrigido['qty'].replace('#N/A', np.nan)
# conversão de formatos numéricos
df_corrigido['primaryValue'] = df_corrigido['primaryValue'].astype(str).str.replace(',', '.', regex=False)
df_corrigido['qty'] = df_corrigido['qty'].astype(str).str.replace(',', '.', regex=False)
df_corrigido['primaryValue'] = pd.to_numeric(df_corrigido['primaryValue'], errors='coerce')
df_corrigido['qty'] = pd.to_numeric(df_corrigido['qty'], errors='coerce')
print("\nValores ausentes por coluna:\n")
print(df_corrigido.isnull().sum())
# criar colunas para países, há nomes que precisam de ser homogeneizados
df_corrigido['Exportador'] = df_corrigido['reporterISO']
df_corrigido['Importador'] = df_corrigido['partnerISO']
# criar df filtrado apenas com exportações
print("Valores únicos na coluna flowDesc:")
print(df_corrigido['flowDesc'].unique())
df_exports = df_corrigido[df_corrigido['flowDesc'] == 'Export'].copy()
print(f"\nTotal de registos de exportação: {len(df_exports)}\n")
# verificar valores ausentes
print("\nValores ausentes após conversão:\n")
print(df_exports[['primaryValue', 'qty']].isna().sum())
# resumo
print("\nResumo estatístico após limpeza:")
stats = df_exports[['primaryValue', 'qty']].describe().round(2)
html_stats = stats.style.format("{:,.2f}")
display(html_stats)
Valores ausentes por coluna: refYear 0 refMonth 0 flowDesc 0 reporterISO 0 partnerISO 0 qty 0 primaryValue 0 unitPrice 0 dtype: int64 Valores únicos na coluna flowDesc: ['Import' 'Export'] Total de registos de exportação: 349622 Valores ausentes após conversão: primaryValue 0 qty 0 dtype: int64 Resumo estatístico após limpeza:
| primaryValue | qty | |
|---|---|---|
| count | 349,622.00 | 349,622.00 |
| mean | 472,476.06 | 34,380.18 |
| std | 12,406,529.79 | 577,240.69 |
| min | 0.00 | 0.00 |
| 25% | 869.00 | 8.90 |
| 50% | 7,749.73 | 200.00 |
| 75% | 62,773.85 | 2,870.99 |
| max | 3,653,069,488.00 | 77,478,428.00 |
1.6 Análise básica de trocas¶
# versão agregada por ano (soma dos valores)
df_export_annual = df_exports.groupby(['refYear', 'reporterISO', 'partnerISO'])['primaryValue'].sum().reset_index()
# dados de exportação agregados
print("\nDados de exportação agregados por ano:")
display(df_export_annual.head())
# principais países exportadores e importadores
top_exporters = df_export_annual.groupby('reporterISO')['primaryValue'].sum().sort_values(ascending=False).head(10)
top_importers = df_export_annual.groupby('partnerISO')['primaryValue'].sum().sort_values(ascending=False).head(10)
print("\nTop 10 países exportadores:")
display(top_exporters)
print("\nTop 10 países importadores:")
display(top_importers)
Dados de exportação agregados por ano:
| refYear | reporterISO | partnerISO | primaryValue | |
|---|---|---|---|---|
| 0 | 2015 | AGO | ARE | 188.12 |
| 1 | 2015 | AGO | BEL | 3347.41 |
| 2 | 2015 | AGO | BRA | 49.50 |
| 3 | 2015 | AGO | COD | 157.44 |
| 4 | 2015 | AGO | COG | 45907.17 |
Top 10 países exportadores:
reporterISO CHN 9.582094e+10 DEU 9.595403e+09 USA 7.148129e+09 MEX 5.745379e+09 VNM 5.534274e+09 IND 3.760615e+09 NLD 3.366292e+09 POL 2.576971e+09 FRA 2.408748e+09 TUR 2.206861e+09 Name: primaryValue, dtype: float64
Top 10 países importadores:
partnerISO USA 4.319084e+10 DEU 1.379284e+10 JPN 1.092044e+10 FRA 9.253455e+09 GBR 8.129058e+09 ITA 5.287905e+09 CAN 5.286796e+09 ESP 4.798290e+09 NLD 4.716897e+09 MEX 3.948564e+09 Name: primaryValue, dtype: float64
2. AED¶
2.1 Variações anuais¶
# análise temporal do valor total de exportações por ano
yearly_stats = df_exports.groupby('refYear').agg({
'primaryValue': 'sum',
'qty': 'sum',
'Exportador': 'nunique',
'Importador': 'nunique'
}).reset_index()
# preço médio por unidade para cada ano
yearly_stats['Preço Médio por Unidade (USD)'] = yearly_stats['primaryValue'] / yearly_stats['qty']
# renomear colunas
yearly_stats = yearly_stats.rename(columns={
'primaryValue': 'Valor Total (USD)',
'qty': 'Quantidade Total',
'Exportador': 'Número de Exportadores',
'Importador': 'Número de Importadores'
})
fig, axes = plt.subplots(2, 2, figsize=(16, 12))
# mil milhões = 1e9
# Grafico 1: Valor Total das Exportações
axes[0, 0].bar(yearly_stats['refYear'], yearly_stats['Valor Total (USD)'] / 1e9, color='steelblue')
axes[0, 0].set_title('Valor Total das Exportações de Máscaras')
axes[0, 0].set_xlabel('Ano')
axes[0, 0].set_ylabel('mil Milhões USD')
axes[0, 0].grid(axis='y', linestyle='--', alpha=0.7)
# Adicionar rótulos de valor
for i, value in enumerate(yearly_stats['Valor Total (USD)']):
axes[0, 0].text(yearly_stats['refYear'].iloc[i], value/1e9 + 2, f'${value/1e9:.1f}mM',
ha='center', va='bottom')
# Grafico 2: Quantidade Total de Máscaras
axes[0, 1].bar(yearly_stats['refYear'], yearly_stats['Quantidade Total'] / 1e9, color='darkgreen')
axes[0, 1].set_title('Quantidade Total de Máscaras Exportadas')
axes[0, 1].set_xlabel('Ano')
axes[0, 1].set_ylabel('mil Milhões de Unidades')
axes[0, 1].grid(axis='y', linestyle='--', alpha=0.7)
# Adicionar rótulos de valor
for i, value in enumerate(yearly_stats['Quantidade Total']):
axes[0, 1].text(yearly_stats['refYear'].iloc[i], value/1e9 + 0.2, f'{value/1e9:.1f}mM',
ha='center', va='bottom')
# Grafico 3: Preço Médio por Unidade
axes[1, 0].bar(yearly_stats['refYear'], yearly_stats['Preço Médio por Unidade (USD)'], color='purple')
axes[1, 0].set_title('Preço Médio por Unidade de Máscara')
axes[1, 0].set_xlabel('Ano')
axes[1, 0].set_ylabel('USD')
axes[1, 0].grid(axis='y', linestyle='--', alpha=0.7)
# Adicionar rótulos de valor
for i, value in enumerate(yearly_stats['Preço Médio por Unidade (USD)']):
axes[1, 0].text(yearly_stats['refYear'].iloc[i], value + 1, f'${value:.2f}',
ha='center', va='bottom')
# Grafico 4: Número de Países Exportadores
axes[1, 1].plot(yearly_stats['refYear'], yearly_stats['Número de Exportadores'], 'o-', color='blue', label='Exportadores')
importers_adjusted = yearly_stats['Número de Importadores'].copy()
importers_adjusted.loc[yearly_stats['refYear'] == 2019] # Marcar 2019 como dados ausentes
mask = yearly_stats['refYear'] != 2019
axes[1, 1].plot(yearly_stats.loc[mask, 'refYear'], importers_adjusted[mask], 's-', color='red', label='Importadores')
axes[1, 1].set_title('Número de Países Envolvidos no Comércio')
axes[1, 1].set_xlabel('Ano')
axes[1, 1].set_ylabel('Número de Países')
axes[1, 1].legend()
axes[1, 1].grid(True, alpha=0.3)
# adicionar nota sobre o ano 2019
axes[1, 1].annotate('Dados de 2019 apenas\nagregados como "World"',
xy=(2019, 100), xytext=(2019, 150),
arrowprops=dict(facecolor='black', shrink=0.05, width=1.5),
ha='center', fontsize=10)
plt.tight_layout()
plt.show()
print("\nComparação de Períodos:")
# tirar 2019 da análise pré-pandemia devido à limitação dos dados
pre_pandemia_ajustado = yearly_stats[yearly_stats['refYear'] < 2019]
durante_pandemia = yearly_stats[(yearly_stats['refYear'] >= 2020) & (yearly_stats['refYear'] <= 2021)]
pos_pandemia = yearly_stats[yearly_stats['refYear'] > 2021]
print(f"Média de Valor Total Pré-Pandemia (2015-2019): ${pre_pandemia_ajustado['Valor Total (USD)'].mean()/1e9:.2f} mil Milhões")
print(f"Média de Valor Total Durante Pandemia (2020-2021): ${durante_pandemia['Valor Total (USD)'].mean()/1e9:.2f} mil Milhões")
print(f"Média de Valor Total Pós-Pandemia (2022-2024): ${pos_pandemia['Valor Total (USD)'].mean()/1e9:.2f} mil Milhões")
print(f"\nMédia de Preço por Unidade Pré-Pandemia (2015-2019): ${pre_pandemia_ajustado['Preço Médio por Unidade (USD)'].mean():.2f}")
print(f"Média de Preço por Unidade Durante Pandemia (2020-2021): ${durante_pandemia['Preço Médio por Unidade (USD)'].mean():.2f}")
print(f"Média de Preço por Unidade Pós-Pandemia (2022-2024): ${pos_pandemia['Preço Médio por Unidade (USD)'].mean():.2f}")
Comparação de Períodos: Média de Valor Total Pré-Pandemia (2015-2019): $9.23 mil Milhões Média de Valor Total Durante Pandemia (2020-2021): $45.43 mil Milhões Média de Valor Total Pós-Pandemia (2022-2024): $12.47 mil Milhões Média de Preço por Unidade Pré-Pandemia (2015-2019): $11.41 Média de Preço por Unidade Durante Pandemia (2020-2021): $18.72 Média de Preço por Unidade Pós-Pandemia (2022-2024): $9.78
2.2 Top Exportadores em $¶
periodos = {
"Pré-Pandemia (2015-2018)": df_exports[df_exports['refYear'] < 2019],
"Durante a Pandemia (2020-2021)": df_exports[(df_exports['refYear'] >= 2020) & (df_exports['refYear'] <= 2021)],
"Pós-Pandemia (2022-2024)": df_exports[df_exports['refYear'] > 2021]
}
#função para top exporters
def top_exportadores(df, n=10):
return (
df.groupby('Exportador')['primaryValue'].sum()
.nlargest(n)
.reset_index()
)
def plot_top(df_top, titulo):
df_top = df_top.sort_values('primaryValue')
plt.figure(figsize=(10, 6))
bars = plt.barh(df_top['Exportador'], df_top['primaryValue'] / 1e9, color='steelblue')
plt.xlabel('Exportações (mil Milhões USD)')
plt.title(titulo)
plt.grid(axis='x', linestyle='--', alpha=0.6)
for bar in bars:
plt.text(bar.get_width() + 0.1, bar.get_y() + bar.get_height()/2, f'{bar.get_width():.1f}mM', va='center')
plt.tight_layout()
plt.show()
# amrazenar resultados e mostrar
tops = {}
for nome, dados in periodos.items():
tops[nome] = top_exportadores(dados)
print(f"\nTop 10 Exportadores - {nome}")
for i, row in tops[nome].iterrows():
print(f"{i+1}. {row['Exportador']}: ${row['primaryValue'] / 1e9:.2f}mM")
plot_top(tops[nome], f'Top 10 Exportadores de Máscaras - {nome}')
Top 10 Exportadores - Pré-Pandemia (2015-2018) 1. CHN: $14.06mM 2. DEU: $3.05mM 3. USA: $2.66mM 4. VNM: $2.09mM 5. MEX: $2.02mM 6. IND: $2.00mM 7. NLD: $0.98mM 8. FRA: $0.74mM 9. GBR: $0.68mM 10. HKG: $0.67mM
Top 10 Exportadores - Durante a Pandemia (2020-2021) 1. CHN: $66.26mM 2. DEU: $3.42mM 3. VNM: $2.49mM 4. USA: $2.11mM 5. MEX: $1.48mM 6. NLD: $1.23mM 7. HKG: $1.00mM 8. KOR: $0.99mM 9. TUR: $0.97mM 10. FRA: $0.92mM
Top 10 Exportadores - Pós-Pandemia (2022-2024) 1. CHN: $15.50mM 2. DEU: $3.13mM 3. USA: $2.38mM 4. MEX: $2.25mM 5. NLD: $1.16mM 6. POL: $1.12mM 7. IND: $1.04mM 8. VNM: $0.95mM 9. TUR: $0.79mM 10. FRA: $0.76mM
2.3 Top Exportadores em qty¶
periodos = {
"Pré-Pandemia (2015-2018)": df_exports[df_exports['refYear'] < 2019],
"Durante a Pandemia (2020-2021)": df_exports[(df_exports['refYear'] >= 2020) & (df_exports['refYear'] <= 2021)],
"Pós-Pandemia (2022-2024)": df_exports[df_exports['refYear'] > 2021]
}
#função para top exporters
def top_exportadores(df, n=10):
return (
df.groupby('Exportador')['qty'].sum()
.nlargest(n)
.reset_index()
)
def plot_top(df_top, titulo):
df_top = df_top.copy()
df_top['qty'] = df_top['qty'] / 1000
df_top = df_top.sort_values('qty')
plt.figure(figsize=(10, 6))
bars = plt.barh(df_top['Exportador'], df_top['qty'], color='steelblue')
plt.xlabel('Exportações (toneladas)')
plt.title(titulo)
plt.grid(axis='x', linestyle='--', alpha=0.6)
for bar in bars:
plt.text(bar.get_width() + 0.1, bar.get_y() + bar.get_height()/2, f'{bar.get_width():.1f} t', va='center')
plt.tight_layout()
plt.show()
# armazenar resultados e mostrar
tops = {}
for nome, dados in periodos.items():
tops[nome] = top_exportadores(dados)
print(f"\nTop 10 Exportadores - {nome}")
for i, row in tops[nome].iterrows():
print(f"{i+1}. {row['Exportador']}: {row['qty'] / 1000:.2f} toneladas")
plot_top(tops[nome], f'Top 10 Exportadores de Máscaras - {nome}')
Top 10 Exportadores - Pré-Pandemia (2015-2018) 1. CHN: 1370895.94 toneladas 2. HKG: 771349.28 toneladas 3. DEU: 207818.22 toneladas 4. NLD: 126099.65 toneladas 5. VNM: 116922.12 toneladas 6. MEX: 112478.94 toneladas 7. IND: 107670.14 toneladas 8. TUR: 55077.03 toneladas 9. USA: 47960.28 toneladas 10. THA: 33598.85 toneladas
Top 10 Exportadores - Durante a Pandemia (2020-2021) 1. CHN: 3302422.32 toneladas 2. DEU: 165005.51 toneladas 3. USA: 160471.86 toneladas 4. NLD: 123349.83 toneladas 5. VNM: 100976.72 toneladas 6. TUR: 77638.38 toneladas 7. POL: 64099.71 toneladas 8. IND: 60624.55 toneladas 9. MEX: 59482.16 toneladas 10. HKG: 44607.69 toneladas
Top 10 Exportadores - Pós-Pandemia (2022-2024) 1. CHN: 2119386.72 toneladas 2. NLD: 236143.53 toneladas 3. USA: 200440.61 toneladas 4. DEU: 188046.24 toneladas 5. MEX: 132135.50 toneladas 6. TUR: 101381.22 toneladas 7. IND: 98644.41 toneladas 8. POL: 85724.99 toneladas 9. VNM: 61756.26 toneladas 10. ESP: 53605.98 toneladas
2.4 Modificações nos Tops¶
# comparar rankings entre períodos
def comparar_rankings(top_a, top_b, nome_a, nome_b):
set_a, set_b = set(top_a['Exportador']), set(top_b['Exportador'])
novos = set_b - set_a
sairam = set_a - set_b
comuns = set_a & set_b
print(f"\n Mudanças do Top 10 entre {nome_a} e {nome_b}:")
if novos:
print(f" Entraram no top 10 em {nome_b}: {', '.join(novos)}")
if sairam:
print(f" Saíram do top 10 em {nome_b}: {', '.join(sairam)}")
# análise de variação de posição
rank_a = {c: i+1 for i, c in enumerate(top_a['Exportador'])}
rank_b = {c: i+1 for i, c in enumerate(top_b['Exportador'])}
mudancas = [(c, rank_a[c], rank_b[c], rank_a[c] - rank_b[c]) for c in comuns]
mudancas.sort(key=lambda x: abs(x[3]), reverse=True)
print("\n Maiores mudanças de posição:")
for c, r1, r2, delta in mudancas[:5]:
direcao = "subiu" if delta > 0 else "desceu"
print(f"{c}: {r1}º -> {r2}º ({direcao} {abs(delta)} posições)")
comparar_rankings(tops["Pré-Pandemia (2015-2018)"], tops["Durante a Pandemia (2020-2021)"],
"Pré-Pandemia", "Durante a Pandemia")
comparar_rankings(tops["Durante a Pandemia (2020-2021)"], tops["Pós-Pandemia (2022-2024)"],
"Durante a Pandemia", "Pós-Pandemia")
Mudanças do Top 10 entre Pré-Pandemia e Durante a Pandemia: Entraram no top 10 em Durante a Pandemia: POL Saíram do top 10 em Durante a Pandemia: THA Maiores mudanças de posição: HKG: 2º -> 10º (desceu 8 posições) USA: 9º -> 3º (subiu 6 posições) MEX: 6º -> 9º (desceu 3 posições) TUR: 8º -> 6º (subiu 2 posições) DEU: 3º -> 2º (subiu 1 posições) Mudanças do Top 10 entre Durante a Pandemia e Pós-Pandemia: Entraram no top 10 em Pós-Pandemia: ESP Saíram do top 10 em Pós-Pandemia: HKG Maiores mudanças de posição: VNM: 5º -> 9º (desceu 4 posições) MEX: 9º -> 5º (subiu 4 posições) NLD: 4º -> 2º (subiu 2 posições) DEU: 2º -> 4º (desceu 2 posições) IND: 8º -> 7º (subiu 1 posições)
3. Construção das redes¶
# função para criar redes por periodo
def create_network_period(df, year, threshold=0):
G = nx.DiGraph(name=f"Rede de Exportação de Máscaras Cirúrgicas - {year}")
# Agrupar total de qty por par de países
grouped = df.groupby(['Exportador', 'Importador'])['qty'].sum().reset_index()
for _, row in grouped.iterrows():
if row['qty'] > threshold:
G.add_edge(row['Exportador'],
row['Importador'],
weight=row['qty']
)
return G
#função para obter posições geográficas
def get_geo_positions(G):
pos = {}
for node in G.nodes():
if node in coords:
pos[node] = (coords[node]['Longitude'], coords[node]['Latitude'])
return pos
all_qty = pd.concat([
periodos["Pré-Pandemia (2015-2018)"]['qty'],
periodos["Durante a Pandemia (2020-2021)"]['qty'],
periodos["Pós-Pandemia (2022-2024)"]['qty']
])
print("Resumo estatístico dos pesos (qty):")
print(all_qty.describe(percentiles=[.25, .5, .75, .9, .95, .99]))
Resumo estatístico dos pesos (qty): count 3.496220e+05 mean 3.438018e+04 std 5.772407e+05 min 0.000000e+00 25% 8.900000e+00 50% 2.000000e+02 75% 2.870992e+03 90% 2.148596e+04 95% 6.436371e+04 99% 5.055140e+05 max 7.747843e+07 Name: qty, dtype: float64
Testar valores para threshold¶
for nome, df in periodos.items():
n_total = df.groupby(['Exportador', 'Importador']).ngroups
n_filtrado = df[df['qty'] > 60000].groupby(['Exportador', 'Importador']).ngroups
print(f"{nome}: Total = {n_total}, Após threshold = {n_filtrado}, % mantido = {n_filtrado / n_total:.2%}")
Pré-Pandemia (2015-2018): Total = 8381, Após threshold = 396, % mantido = 4.72% Durante a Pandemia (2020-2021): Total = 8067, Após threshold = 609, % mantido = 7.55% Pós-Pandemia (2022-2024): Total = 8111, Após threshold = 553, % mantido = 6.82%
Ler ficheiro com coordenadas e montar redes¶
# dataFrame de coordenadas dos países
coords_df = pd.read_csv('in-nodes-All.csv')
coords = coords_df.set_index('Label')[['Longitude', 'Latitude']].to_dict('index')
# montar redes agregadas por período
networks_periodos = {
"Pré-Pandemia": create_network_period(periodos["Pré-Pandemia (2015-2018)"], "Pré", threshold=60000),
"Durante Pandemia": create_network_period(periodos["Durante a Pandemia (2020-2021)"], "Durante", threshold=60000),
"Pós-Pandemia": create_network_period(periodos["Pós-Pandemia (2022-2024)"], "Pós", threshold=60000),
}
3.1 Rede Pré-Pandemia - Spring Layout¶
G = create_network_period(periodos["Pré-Pandemia (2015-2018)"], "Pré", threshold=2000)
#spring layout
pos = nx.spring_layout(G)
plt.figure(figsize=(12, 8))
nx.draw(G, pos, node_color='lightblue',node_size=50,edge_color='gray',alpha=0.6,with_labels=False,arrows=True,arrowsize=10)
plt.title("Rede de Exportações - Pré-Pandemia")
plt.show()
3.2 Análise por Períodos - Com coordenadas Geo¶
# desenhar redes
for title, G in networks_periodos.items():
plt.figure(figsize=(15, 10))
pos = get_geo_positions(G)
# desenhar nós
nodes = list(pos.keys())
node_sizes = [G.degree(n)*10 for n in nodes]
node_color='b'
edge_width = 0.5
nx.draw_networkx_labels(G, pos, font_size=14, font_color='w')
nx.draw_networkx_nodes(G, pos, node_size=node_sizes, node_color=node_color, alpha=0.7)
nx.draw_networkx_edges(G, pos, connectionstyle='arc3,rad=0.2', edge_color='green', width=edge_width, arrowstyle='->', arrowsize=25)
plt.title(f"Rede de Exportação de Máscaras Cirúrgicas - {title}", fontsize=16)
plt.xticks([])
plt.yticks([])
plt.xlim(-180, 180)
plt.ylim(-90, 90)
plt.show()
# desenhar redes sem labels e apenas ocm uma amostra das arestas
for title, G in networks_periodos.items():
pos = get_geo_positions(G)
# filtrar arestas para reduzir densidade, 800 escolhidas de forma aleatória por agora
all_edges = list(G.edges())
sample_size = min(800, len(all_edges))
edges_sample = random.sample(all_edges, sample_size)
# desenhar nós
nodes = list(pos.keys())
node_sizes = [min(G.degree(n)*2, 100) for n in nodes]
node_color='b'
edge_width = 0.5
plt.figure(figsize=(15, 10))
nx.draw_networkx_nodes(G, pos, node_size=node_sizes, node_color=node_color, alpha=0.8)
nx.draw_networkx_edges(G, pos, edgelist=edges_sample, connectionstyle='arc3,rad=0.2',
edge_color='green', width=edge_width, arrowstyle='->', arrowsize=25, alpha=0.6)
plt.title(f"Rede de Exportação de Máscaras Cirúrgicas - {title}", fontsize=16)
plt.xticks([])
plt.yticks([])
plt.xlim(-180, 180)
plt.ylim(-90, 90)
plt.show()
Inserir Mapa¶
import cartopy.crs as ccrs
import cartopy.feature as cfeature
for title, G in networks_periodos.items():
pos = get_geo_positions(G)
all_edges = list(G.edges())
sample_size = min(800, len(all_edges))
edges_sample = random.sample(all_edges, sample_size)
nodes = list(pos.keys())
node_sizes = [min(G.degree(n)*2, 100) for n in nodes]
fig = plt.figure(figsize=(15, 10))
ax = plt.axes(projection=ccrs.PlateCarree())
ax.add_feature(cfeature.LAND, facecolor='lightgray')
ax.add_feature(cfeature.OCEAN, facecolor='lightblue')
ax.add_feature(cfeature.COASTLINE, linewidth=0.5)
ax.add_feature(cfeature.BORDERS, linewidth=0.5, alpha=0.5)
nx.draw_networkx_nodes(G, pos, node_size=node_sizes, node_color='b', alpha=0.5,ax=ax)
nx.draw_networkx_edges(G, pos, edgelist=edges_sample, edge_color='darkgreen', width=0.5, alpha=0.4,arrows=True, arrowsize=5, connectionstyle='arc3,rad=0.1', ax=ax)
ax.set_global()
plt.title(f"Rede de Exportação de Máscaras Cirúrgicas - {title}", fontsize=16)
plt.show()
filtrar top 10% das arestas com maior peso e eliminar a seleção aleatório das 800 arestas¶
for title, G in networks_periodos.items():
pos = get_geo_positions(G)
edges_with_weights = [(u, v, d['weight']) for u, v, d in G.edges(data=True)]
edges_with_weights.sort(key=lambda x: x[2], reverse=True)
n_top = int(len(edges_with_weights) * 0.10)
edges_sample = [(u, v) for u, v, w in edges_with_weights[:n_top]]
# espessura proporcional ao peso
raw_weights = [w for _, _, w in edges_with_weights[:n_top]]
w_min, w_max = min(raw_weights), max(raw_weights)
edge_widths = [min(0.5 + w / 100_000_000, 5) for w in raw_weights]
nodes = list(pos.keys())
node_sizes = [min(G.degree(n) * 2, 100) for n in nodes]
fig = plt.figure(figsize=(15, 10))
ax = plt.axes(projection=ccrs.PlateCarree())
ax.add_feature(cfeature.LAND, facecolor='lightgray')
ax.add_feature(cfeature.OCEAN, facecolor='lightblue')
ax.add_feature(cfeature.COASTLINE, linewidth=0.5)
ax.add_feature(cfeature.BORDERS, linewidth=0.5, alpha=0.5)
nx.draw_networkx_nodes(G, pos, node_size=node_sizes, node_color='b', alpha=0.5, ax=ax)
nx.draw_networkx_edges(G,pos,edgelist=edges_sample,width=edge_widths,edge_color='darkgreen',
alpha=0.4,arrows=True,arrowsize=5,connectionstyle='arc3,rad=0.1',ax=ax)
ax.set_global()
plt.title(f"Rede de Exportação de Máscaras Cirúrgicas - {title}", fontsize=16)
plt.show()
3.2.1. Estatísticas básicas¶
network_stats_per_df = pd.DataFrame([
{
'Período': year,
'Nós': G.number_of_nodes(),
'Arestas': G.number_of_edges(),
'Densidade': nx.density(G),
'Grau Médio': sum(dict(G.degree()).values()) / G.number_of_nodes(),
'Componentes Conectadas Fracamente': nx.number_weakly_connected_components(G),
'Tamanho da Maior Componente': len(max(nx.weakly_connected_components(G), key=len))
}
for year, G in networks_periodos.items()
])
# mostrar a tabela
print("\nEstatísticas das Redes de Exportação por Ano:")
display(network_stats_per_df)
Estatísticas das Redes de Exportação por Ano:
| Período | Nós | Arestas | Densidade | Grau Médio | Componentes Conectadas Fracamente | Tamanho da Maior Componente | |
|---|---|---|---|---|---|---|---|
| 0 | Pré-Pandemia | 159 | 1136 | 0.045219 | 14.289308 | 1 | 159 |
| 1 | Durante Pandemia | 175 | 1323 | 0.043448 | 15.120000 | 1 | 175 |
| 2 | Pós-Pandemia | 167 | 1361 | 0.049095 | 16.299401 | 1 | 167 |
3.2.2. Medidas de Centralidade¶
# clcular centralidades para cada período e guardar em df
centralidade_periodos = {}
for periodo, G in networks_periodos.items():
print(f"\n=== Centralidades - {periodo} ===")
# calcular centralidades
grau_total = dict(G.degree())
grau_in = dict(G.in_degree())
grau_out = dict(G.out_degree())
betweenness = nx.betweenness_centrality(G, normalized=True)
closeness = nx.closeness_centrality(G)
eigenvector = nx.eigenvector_centrality(G)
pagerank = nx.pagerank(G, alpha=0.85)
#construir df
df_centralidades = pd.DataFrame({
'Nó': list(G.nodes()),
'Grau Total': pd.Series(grau_total),
'Grau Entrada': pd.Series(grau_in),
'Grau Saída': pd.Series(grau_out),
'Betweenness': pd.Series(betweenness),
'Closeness': pd.Series(closeness),
'Eigenvector': pd.Series(eigenvector),
'Page Rank': pd.Series(pagerank),
})
df_centralidades = df_centralidades.set_index('Nó')
df_centralidades = df_centralidades.sort_values('Grau Total', ascending=False)
#guardar
centralidade_periodos[periodo] = df_centralidades
display(df_centralidades.head(10))
=== Centralidades - Pré-Pandemia ===
| Grau Total | Grau Entrada | Grau Saída | Betweenness | Closeness | Eigenvector | Page Rank | |
|---|---|---|---|---|---|---|---|
| Nó | |||||||
| CHN | 152 | 19 | 133 | 0.114124 | 0.221711 | 0.154300 | 0.012788 |
| DEU | 107 | 37 | 70 | 0.076492 | 0.261302 | 0.260183 | 0.073282 |
| HKG | 102 | 10 | 92 | 0.023828 | 0.201833 | 0.083668 | 0.004925 |
| USA | 88 | 33 | 55 | 0.098182 | 0.258990 | 0.215394 | 0.044367 |
| IND | 78 | 9 | 69 | 0.035096 | 0.199087 | 0.070155 | 0.003762 |
| TUR | 77 | 14 | 63 | 0.022569 | 0.207559 | 0.131956 | 0.005606 |
| FRA | 69 | 31 | 38 | 0.034094 | 0.243882 | 0.220822 | 0.035796 |
| NLD | 67 | 24 | 43 | 0.019269 | 0.230440 | 0.188709 | 0.023910 |
| POL | 56 | 23 | 33 | 0.012622 | 0.232268 | 0.197858 | 0.026402 |
| ITA | 56 | 25 | 31 | 0.019892 | 0.237934 | 0.190068 | 0.023499 |
=== Centralidades - Durante Pandemia ===
| Grau Total | Grau Entrada | Grau Saída | Betweenness | Closeness | Eigenvector | Page Rank | |
|---|---|---|---|---|---|---|---|
| Nó | |||||||
| CHN | 197 | 34 | 163 | 0.198836 | 0.284665 | 0.188756 | 0.017235 |
| DEU | 114 | 53 | 61 | 0.064766 | 0.335213 | 0.277015 | 0.065098 |
| USA | 109 | 45 | 64 | 0.084185 | 0.306563 | 0.220061 | 0.094732 |
| TUR | 87 | 15 | 72 | 0.021002 | 0.235972 | 0.112164 | 0.005353 |
| FRA | 87 | 41 | 46 | 0.031644 | 0.296428 | 0.233230 | 0.052897 |
| NLD | 82 | 35 | 47 | 0.017209 | 0.273800 | 0.206814 | 0.021847 |
| HKG | 76 | 24 | 52 | 0.017393 | 0.256199 | 0.128535 | 0.009771 |
| GBR | 68 | 33 | 35 | 0.010632 | 0.269683 | 0.197584 | 0.015855 |
| IND | 66 | 11 | 55 | 0.009343 | 0.228457 | 0.065748 | 0.004746 |
| ITA | 64 | 30 | 34 | 0.014006 | 0.265688 | 0.202409 | 0.027568 |
=== Centralidades - Pós-Pandemia ===
| Grau Total | Grau Entrada | Grau Saída | Betweenness | Closeness | Eigenvector | Page Rank | |
|---|---|---|---|---|---|---|---|
| Nó | |||||||
| CHN | 176 | 29 | 147 | 0.139261 | 0.258890 | 0.170584 | 0.010085 |
| DEU | 111 | 47 | 64 | 0.064736 | 0.305737 | 0.251804 | 0.064468 |
| USA | 107 | 43 | 64 | 0.106485 | 0.305737 | 0.206620 | 0.091629 |
| TUR | 95 | 19 | 76 | 0.036093 | 0.241372 | 0.129955 | 0.005128 |
| FRA | 82 | 38 | 44 | 0.028375 | 0.284092 | 0.215068 | 0.045997 |
| NLD | 77 | 30 | 47 | 0.010932 | 0.260995 | 0.180750 | 0.019284 |
| IND | 72 | 12 | 60 | 0.016060 | 0.222933 | 0.071619 | 0.002990 |
| GBR | 72 | 33 | 39 | 0.016308 | 0.267520 | 0.199229 | 0.015623 |
| POL | 72 | 34 | 38 | 0.011436 | 0.269768 | 0.204632 | 0.028959 |
| ITA | 67 | 32 | 35 | 0.011521 | 0.265309 | 0.205465 | 0.025458 |
for periodo, G in networks_periodos.items():
print(f"\n=== Centralidades - {periodo} ===")
# maior componente fortemente conectada
largest_scc = max(nx.strongly_connected_components(G), key=len)
G_scc = G.subgraph(largest_scc).copy()
# excentricidade para a maior componente fortemente conectada
excentricidade = nx.eccentricity(G_scc)
#df com os resultados
df_excentricidade = pd.DataFrame({
'Excentricidade': pd.Series(excentricidade),
})
df_excentricidade = df_excentricidade.sort_values('Excentricidade', ascending=True)
display(df_excentricidade.head(10))
raio = min(excentricidade.values())
print(f"Raio do grafo para {periodo}: {raio}")
=== Centralidades - Pré-Pandemia ===
| Excentricidade | |
|---|---|
| GBR | 2 |
| CHN | 2 |
| DEU | 2 |
| NLD | 2 |
| POL | 2 |
| SWE | 2 |
| ITA | 2 |
| USA | 2 |
| VNM | 2 |
| FRA | 2 |
Raio do grafo para Pré-Pandemia: 2 === Centralidades - Durante Pandemia ===
| Excentricidade | |
|---|---|
| CHN | 1 |
| UKR | 2 |
| IND | 2 |
| SGP | 2 |
| GBR | 2 |
| USA | 2 |
| ITA | 2 |
| IDN | 2 |
| SVK | 2 |
| NLD | 2 |
Raio do grafo para Durante Pandemia: 1 === Centralidades - Pós-Pandemia ===
| Excentricidade | |
|---|---|
| GBR | 2 |
| TUR | 2 |
| ESP | 2 |
| CHE | 2 |
| IND | 2 |
| CHN | 2 |
| USA | 2 |
| CZE | 2 |
| NLD | 2 |
| DEU | 2 |
Raio do grafo para Pós-Pandemia: 2
3.2.3 - Propriedades de Conectividade¶
for periodo, G in networks_periodos.items():
print(f'\n\n--- Período {periodo} ---')
print('É fortemente conexo?', nx.is_strongly_connected(G))
print('É fracamente conexo?', nx.is_weakly_connected(G))
print('Número de componentes fortemente conexas:', nx.number_strongly_connected_components(G))
print('Número de componentes fracamente conexas:', nx.number_weakly_connected_components(G))
print('\nTop 3 componentes fortemente conexas:')
for c in sorted(nx.strongly_connected_components(G), key=len, reverse=True)[:3]:
print(c)
print('\nTop 3 componentes fracamente conexas:')
for c in sorted(nx.weakly_connected_components(G), key=len, reverse=True)[:3]:
print(c)
--- Período Pré-Pandemia ---
É fortemente conexo? False
É fracamente conexo? True
Número de componentes fortemente conexas: 91
Número de componentes fracamente conexas: 1
Top 3 componentes fortemente conexas:
{'GBR', 'ISR', 'MYS', 'MEX', 'PRY', 'BEL', 'LTU', 'DOM', 'HUN', 'SLV', 'PHL', 'ARE', 'KOR', 'MAR', 'ROU', 'SVK', 'PAK', 'NLD', 'DEU', 'IRL', 'UKR', 'IDN', 'MMR', 'EST', 'SVN', 'COL', 'CHN', 'MDG', 'IND', 'SGP', 'CHL', 'CHE', 'SAU', 'TUR', 'PRT', 'MDA', 'BLR', 'LUX', 'LAO', 'KAZ', 'BRA', 'ESP', 'BHR', 'HRV', 'NOR', 'BGR', 'LVA', 'MKD', 'THA', 'USA', 'CZE', 'DNK', 'POL', 'SWE', 'SRB', 'AUS', 'ITA', 'KWT', 'ZAF', 'JPN', 'KHM', 'ECU', 'VNM', 'RUS', 'HKG', 'FRA', 'PER', 'FIN', 'BIH'}
{'JOR'}
{'QAT'}
Top 3 componentes fracamente conexas:
{'UGA', 'MEX', 'MYS', 'URY', 'BEL', 'SLV', 'PHL', 'ARE', 'AZE', 'DEU', 'IRL', 'COD', 'MMR', 'AUT', 'GMB', 'TGO', 'MDG', 'SDN', 'SAU', 'MUS', 'TUR', 'KAZ', 'LUX', 'BWA', 'BRA', 'NOR', 'MRT', 'SRB', 'SYR', 'JPN', 'KHM', 'BHS', 'RUS', 'DJI', 'UZB', 'HKG', 'TTO', 'GUM', 'AFG', 'RWA', 'CHN', 'IND', 'SGP', 'MDV', 'PRT', 'BHR', 'SEN', 'SOM', 'ESP', 'GTM', 'SWZ', 'IRN', 'USA', 'COM', 'DNK', 'CRI', 'EGY', 'ITA', 'NAM', 'YEM', 'GHA', 'CYP', 'LBN', 'MAC', 'GUY', 'ALB', 'ETH', 'GAB', 'KEN', 'NZL', 'TZA', 'ZMB', 'DOM', 'PRY', 'MNG', 'CUB', 'ROU', 'SVK', 'NLD', 'UKR', 'DZA', 'EST', 'COL', 'FJI', 'CHE', 'CYM', 'BOL', 'PYF', 'BRN', 'HRV', 'THA', 'JAM', 'POL', 'ARG', 'JOR', 'MLT', 'CAN', 'KGZ', 'PAN', 'AND', 'FIN', 'GBR', 'NIC', 'ISR', 'COG', 'VEN', 'TKM', 'BRB', 'NGA', 'LTU', 'HUN', 'TJK', 'KOR', 'MAR', 'IRQ', 'PAK', 'GRC', 'LSO', 'ISL', 'IDN', 'SVN', 'NPL', 'PRK', 'CHL', 'HND', 'BGD', 'LBY', 'PNG', 'TUN', 'LAO', 'MDA', 'BLR', 'CIV', 'QAT', 'BEN', 'MOZ', 'GEO', 'MWI', 'BGR', 'LVA', 'MKD', 'CZE', 'SWE', 'KWT', 'CMR', 'HTI', 'AUS', 'ZAF', 'ECU', 'VNM', 'NCL', 'FRA', 'OMN', 'LKA', 'PER', 'AGO', 'ZWE', 'GIN', 'BIH'}
--- Período Durante Pandemia ---
É fortemente conexo? False
É fracamente conexo? True
Número de componentes fortemente conexas: 96
Número de componentes fracamente conexas: 1
Top 3 componentes fortemente conexas:
{'URY', 'MEX', 'MYS', 'BEL', 'SLV', 'PHL', 'DEU', 'IRL', 'MMR', 'MDG', 'TUR', 'LUX', 'BRA', 'NOR', 'SRB', 'JPN', 'KHM', 'RUS', 'UZB', 'HKG', 'CHN', 'IND', 'SGP', 'PRT', 'BHR', 'ESP', 'GTM', 'USA', 'DNK', 'EGY', 'ITA', 'GHA', 'CYP', 'MAC', 'PRY', 'DOM', 'ROU', 'SVK', 'NLD', 'UKR', 'EST', 'COL', 'CHE', 'BOL', 'HRV', 'THA', 'POL', 'ARG', 'CAN', 'FIN', 'GBR', 'NIC', 'ISR', 'LTU', 'HUN', 'KOR', 'MAR', 'PAK', 'GRC', 'IDN', 'SVN', 'CHL', 'TUN', 'LAO', 'MDA', 'BLR', 'GEO', 'ARM', 'BGR', 'MKD', 'LVA', 'CZE', 'SWE', 'AUS', 'ZAF', 'ECU', 'VNM', 'FRA', 'PER', 'BIH'}
{'AFG'}
{'AGO'}
Top 3 componentes fracamente conexas:
{'UGA', 'URY', 'MYS', 'MEX', 'BEL', 'SLV', 'PHL', 'ARE', 'AZE', 'DEU', 'IRL', 'COD', 'MMR', 'AUT', 'GMB', 'TGO', 'MDG', 'SDN', 'MUS', 'SAU', 'TUR', 'KAZ', 'LUX', 'BWA', 'BRA', 'MRT', 'NOR', 'LBR', 'TLS', 'SRB', 'SYR', 'JPN', 'KHM', 'BHS', 'CPV', 'RUS', 'DJI', 'UZB', 'HKG', 'TTO', 'AFG', 'MNE', 'RWA', 'CHN', 'SGP', 'IND', 'TCD', 'MDV', 'PRT', 'BHR', 'SEN', 'SUR', 'GRD', 'SOM', 'ESP', 'GTM', 'SWZ', 'IRN', 'USA', 'COM', 'DNK', 'CRI', 'EGY', 'ITA', 'NAM', 'YEM', 'BLZ', 'GHA', 'BFA', 'CYP', 'GUY', 'LBN', 'MAC', 'ALB', 'SLE', 'ETH', 'GAB', 'KEN', 'NZL', 'PRY', 'DOM', 'TZA', 'ZMB', 'MNG', 'CUB', 'ROU', 'SVK', 'NLD', 'UKR', 'DZA', 'EST', 'COL', 'NER', 'FJI', 'CHE', 'CYM', 'BOL', 'PYF', 'BRN', 'HRV', 'THA', 'JAM', 'POL', 'ARG', 'JOR', 'MLT', 'CAN', 'DMA', 'KGZ', 'PAN', 'AND', 'FIN', 'GBR', 'NIC', 'COG', 'ISR', 'TKM', 'VEN', 'BRB', 'NGA', 'LTU', 'HUN', 'TJK', 'KOR', 'MAR', 'PSE', 'PAK', 'GRC', 'IRQ', 'LSO', 'ISL', 'IDN', 'SVN', 'NPL', 'PRK', 'CHL', 'HND', 'BGD', 'LBY', 'TUN', 'PNG', 'LAO', 'MDA', 'BLR', 'CIV', 'BEN', 'MWI', 'MOZ', 'GEO', 'QAT', 'ARM', 'SSD', 'BGR', 'LVA', 'MKD', 'VCT', 'CZE', 'SWE', 'CMR', 'HTI', 'KWT', 'AUS', 'MLI', 'ZAF', 'ECU', 'VNM', 'NCL', 'FRA', 'PER', 'LKA', 'OMN', 'AGO', 'ZWE', 'GIN', 'BIH'}
--- Período Pós-Pandemia ---
É fortemente conexo? False
É fracamente conexo? True
Número de componentes fortemente conexas: 94
Número de componentes fracamente conexas: 1
Top 3 componentes fortemente conexas:
{'GBR', 'ISR', 'NZL', 'MEX', 'URY', 'PRY', 'BEL', 'LTU', 'DOM', 'HUN', 'SLV', 'MYS', 'PHL', 'KOR', 'MAR', 'ROU', 'SVK', 'PAK', 'GRC', 'DEU', 'IRL', 'NLD', 'UKR', 'IDN', 'AUT', 'MMR', 'EST', 'SVN', 'COL', 'CHN', 'MDG', 'IND', 'SGP', 'CHL', 'CHE', 'SAU', 'TUN', 'BOL', 'TUR', 'LAO', 'PRT', 'KAZ', 'MDA', 'BHR', 'BRA', 'ESP', 'GEO', 'GTM', 'HRV', 'NOR', 'BGR', 'LVA', 'MKD', 'THA', 'USA', 'CZE', 'DNK', 'POL', 'ARG', 'SWE', 'AUS', 'ITA', 'SRB', 'ZAF', 'JPN', 'KHM', 'VNM', 'CAN', 'UZB', 'HKG', 'FRA', 'GHA', 'FIN', 'BIH'}
{'ECU'}
{'ARE'}
Top 3 componentes fracamente conexas:
{'UGA', 'URY', 'MEX', 'MYS', 'BEL', 'SLV', 'PHL', 'ARE', 'AZE', 'DEU', 'IRL', 'COD', 'MMR', 'AUT', 'TGO', 'MDG', 'SDN', 'MUS', 'SAU', 'TUR', 'KAZ', 'LUX', 'BWA', 'BRA', 'MRT', 'NOR', 'LBR', 'SRB', 'SYR', 'JPN', 'KHM', 'BHS', 'RUS', 'DJI', 'UZB', 'HKG', 'TTO', 'AFG', 'MNE', 'GRL', 'FRO', 'RWA', 'CHN', 'IND', 'SGP', 'MDV', 'PRT', 'BHR', 'SEN', 'SUR', 'SOM', 'ESP', 'GTM', 'SWZ', 'IRN', 'USA', 'DNK', 'CRI', 'EGY', 'ITA', 'NAM', 'BLZ', 'YEM', 'GHA', 'BFA', 'CYP', 'GUY', 'LBN', 'MAC', 'ALB', 'ETH', 'GAB', 'KEN', 'NZL', 'PRY', 'DOM', 'TZA', 'ZMB', 'MNG', 'CUB', 'ROU', 'SVK', 'NLD', 'UKR', 'DZA', 'EST', 'COL', 'FJI', 'CHE', 'CYM', 'BOL', 'PYF', 'BRN', 'HRV', 'THA', 'JAM', 'POL', 'ARG', 'JOR', 'MLT', 'CAN', 'KGZ', 'PAN', 'AND', 'GNQ', 'FIN', 'GBR', 'NIC', 'ISR', 'COG', 'TKM', 'VEN', 'BRB', 'NGA', 'LTU', 'HUN', 'TJK', 'KOR', 'MAR', 'PAK', 'GRC', 'IRQ', 'LSO', 'ISL', 'IDN', 'SVN', 'NPL', 'PRK', 'CHL', 'HND', 'BGD', 'LBY', 'PNG', 'TUN', 'LAO', 'MDA', 'BLR', 'CIV', 'BEN', 'MWI', 'MOZ', 'GEO', 'QAT', 'ARM', 'BGR', 'LVA', 'MKD', 'CZE', 'SWE', 'CMR', 'HTI', 'KWT', 'AUS', 'MLI', 'CUW', 'ZAF', 'ECU', 'VNM', 'NCL', 'FRA', 'LKA', 'OMN', 'PER', 'AGO', 'ZWE', 'GIN', 'BIH'}
3.2.4 - Medidas de Distância¶
G_pre = create_network_period(periodos["Pré-Pandemia (2015-2018)"], "Pré", threshold=0)
G_durante = create_network_period(periodos["Durante a Pandemia (2020-2021)"], "Durante", threshold=0)
G_pos = create_network_period(periodos["Pós-Pandemia (2022-2024)"], "Pós", threshold=0)
# dicionários de graus
grau_entrada_pre = dict(G_pre.in_degree())
grau_saida_pre = dict(G_pre.out_degree())
grau_entrada_durante = dict(G_durante.in_degree())
grau_saida_durante = dict(G_durante.out_degree())
# top países por grau de entrada
top_entrada_pre = pd.Series(grau_entrada_pre).sort_values(ascending=False).head(10)
top_entrada_durante = pd.Series(grau_entrada_durante).sort_values(ascending=False).head(10)
# top países por grau de entrada
top_saida_pre = pd.Series(grau_saida_pre).sort_values(ascending=False).head(10)
top_saida_durante = pd.Series(grau_saida_durante).sort_values(ascending=False).head(10)
# plot
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
fig.suptitle("Top 10 Países por Grau de Entrada e Saída", fontsize=16)
top_entrada_pre.plot(kind="bar", ax=axes[0, 0], title="Entrada - Pré-pandemia", color='steelblue')
top_entrada_durante.plot(kind="bar", ax=axes[0, 1], title="Entrada - Durante pandemia", color='darkorange')
top_saida_pre.plot(kind="bar", ax=axes[1, 0], title="Saída - Pré-pandemia", color='steelblue')
top_saida_durante.plot(kind="bar", ax=axes[1, 1], title="Saída - Durante pandemia", color='darkorange')
for ax in axes.flatten():
ax.set_ylabel("Número de conexões")
plt.tight_layout(rect=[0, 0.03, 1, 0.95])
plt.show()
def calcular_medidas_distancia(G, periodo, top_n=10):
print(f"\n=== Medidas de Distância - {periodo} ===")
# identificar a maior componente fortemente conectada (SCC)
scc = list(nx.strongly_connected_components(G))
scc.sort(key=len, reverse=True) # ordenar por tamanho
maior_scc = scc[0]
G_scc = G.subgraph(maior_scc).copy() # fazer o subgrafo da primeira - maior
print(f"Maior componente fortemente conectada tem {len(G_scc)} nós.")
print()
#grande parte do código destas medidas - abaixo e algum acima - é das aulas, dos notebooks do professor, adaptei apenas
# Excentricidade: Top 5 menor e maior
excent = nx.eccentricity(G_scc)
excent_df = pd.DataFrame(list(excent.items()), columns=['Nó', 'Excentricidade'])
excent_df = excent_df.sort_values('Excentricidade')
print("Top 5 nós com menor excentricidade (mais centrais):")
display(excent_df.head(5))
print("Top 5 nós com maior excentricidade (mais periféricos):")
display(excent_df.tail(5))
print()
# Raio
r = nx.radius(G_scc)
print('Raio de G:', r)
print()
# Diâmetro
d = nx.diameter(G_scc)
print('Diâmetro de G:', d)
print()
# Centro
centro = nx.center(G_scc)
print('Centro de G (nodos com excentricidade = raio):', centro)
print()
# Periferia
periferia = nx.periphery(G_scc)
print('Periferia de G (nodos com excentricidade = diâmetro):', periferia)
print()
# Graus de entrada e saída: Top 10
in_deg = dict(G.in_degree())
out_deg = dict(G.out_degree())
in_deg_df = pd.DataFrame(list(in_deg.items()), columns=['Nó', 'Grau de Entrada'])
out_deg_df = pd.DataFrame(list(out_deg.items()), columns=['Nó', 'Grau de Saída'])
in_deg_df = in_deg_df.sort_values('Grau de Entrada', ascending=False)
out_deg_df = out_deg_df.sort_values('Grau de Saída', ascending=False)
print(f"Top {top_n} nós com maior grau de entrada:")
display(in_deg_df.head(top_n))
print(f"Top {top_n} nós com maior grau de saída:")
display(out_deg_df.head(top_n))
print()
# Grau médio
gm_in = sum(in_deg.values()) / len(G.nodes)
gm_out = sum(out_deg.values()) / len(G.nodes)
print('Grau médio de entrada de G:', gm_in)
print('Grau médio de saída de G:', gm_out)
print()
# Average path length
apl = nx.average_shortest_path_length(G_scc)
print('Average path length de G:', apl)
print()
# aplicar a cada período
for periodo, G in networks_periodos.items():
calcular_medidas_distancia(G, periodo, top_n=10)
=== Medidas de Distância - Pré-Pandemia === Maior componente fortemente conectada tem 69 nós. Top 5 nós com menor excentricidade (mais centrais):
| Nó | Excentricidade | |
|---|---|---|
| 0 | GBR | 2 |
| 26 | CHN | 2 |
| 18 | DEU | 2 |
| 17 | NLD | 2 |
| 52 | POL | 2 |
Top 5 nós com maior excentricidade (mais periféricos):
| Nó | Excentricidade | |
|---|---|---|
| 57 | KWT | 5 |
| 61 | ECU | 5 |
| 66 | PER | 5 |
| 32 | SAU | 6 |
| 42 | BHR | 7 |
Raio de G: 2 Diâmetro de G: 7 Centro de G (nodos com excentricidade = raio): ['GBR', 'NLD', 'DEU', 'CHN', 'USA', 'POL', 'SWE', 'ITA', 'VNM', 'HKG', 'FRA'] Periferia de G (nodos com excentricidade = diâmetro): ['BHR'] Top 10 nós com maior grau de entrada:
| Nó | Grau de Entrada | |
|---|---|---|
| 17 | DEU | 37 |
| 21 | USA | 33 |
| 29 | FRA | 31 |
| 30 | GBR | 26 |
| 32 | ITA | 25 |
| 44 | RUS | 25 |
| 34 | NLD | 24 |
| 40 | SWE | 23 |
| 35 | POL | 23 |
| 26 | DNK | 22 |
Top 10 nós com maior grau de saída:
| Nó | Grau de Saída | |
|---|---|---|
| 16 | CHN | 133 |
| 84 | HKG | 92 |
| 17 | DEU | 70 |
| 3 | IND | 69 |
| 130 | TUR | 63 |
| 21 | USA | 55 |
| 34 | NLD | 43 |
| 29 | FRA | 38 |
| 135 | VNM | 33 |
| 35 | POL | 33 |
Grau médio de entrada de G: 7.144654088050315 Grau médio de saída de G: 7.144654088050315 Average path length de G: 2.2440323955669226 === Medidas de Distância - Durante Pandemia === Maior componente fortemente conectada tem 80 nós. Top 5 nós com menor excentricidade (mais centrais):
| Nó | Excentricidade | |
|---|---|---|
| 20 | CHN | 1 |
| 39 | UKR | 2 |
| 21 | IND | 2 |
| 22 | SGP | 2 |
| 50 | GBR | 2 |
Top 5 nós com maior excentricidade (mais periféricos):
| Nó | Excentricidade | |
|---|---|---|
| 44 | HRV | 3 |
| 28 | DNK | 3 |
| 79 | BIH | 3 |
| 32 | CYP | 4 |
| 47 | ARG | 4 |
Raio de G: 1 Diâmetro de G: 4 Centro de G (nodos com excentricidade = raio): ['CHN'] Periferia de G (nodos com excentricidade = diâmetro): ['CYP', 'ARG'] Top 10 nós com maior grau de entrada:
| Nó | Grau de Entrada | |
|---|---|---|
| 17 | DEU | 53 |
| 12 | USA | 45 |
| 22 | FRA | 41 |
| 29 | NLD | 35 |
| 5 | CHN | 34 |
| 23 | GBR | 33 |
| 26 | ITA | 30 |
| 13 | BEL | 28 |
| 20 | ESP | 27 |
| 30 | POL | 25 |
Top 10 nós com maior grau de saída:
| Nó | Grau de Saída | |
|---|---|---|
| 5 | CHN | 163 |
| 43 | TUR | 72 |
| 12 | USA | 64 |
| 17 | DEU | 61 |
| 8 | IND | 55 |
| 7 | HKG | 52 |
| 29 | NLD | 47 |
| 22 | FRA | 46 |
| 160 | VNM | 40 |
| 23 | GBR | 35 |
Grau médio de entrada de G: 7.56 Grau médio de saída de G: 7.56 Average path length de G: 2.0765822784810126 === Medidas de Distância - Pós-Pandemia === Maior componente fortemente conectada tem 74 nós. Top 5 nós com menor excentricidade (mais centrais):
| Nó | Excentricidade | |
|---|---|---|
| 0 | GBR | 2 |
| 38 | TUR | 2 |
| 45 | ESP | 2 |
| 34 | CHE | 2 |
| 31 | IND | 2 |
Top 5 nós com maior excentricidade (mais periféricos):
| Nó | Excentricidade | |
|---|---|---|
| 4 | URY | 4 |
| 68 | UZB | 4 |
| 35 | SAU | 4 |
| 58 | ARG | 4 |
| 37 | BOL | 5 |
Raio de G: 2 Diâmetro de G: 5 Centro de G (nodos com excentricidade = raio): ['GBR', 'HUN', 'MYS', 'KOR', 'SVK', 'PAK', 'DEU', 'NLD', 'CHN', 'IND', 'CHE', 'TUR', 'ESP', 'USA', 'CZE', 'DNK', 'POL', 'SWE', 'ITA', 'KHM', 'VNM', 'CAN', 'HKG', 'FRA'] Periferia de G (nodos com excentricidade = diâmetro): ['BOL'] Top 10 nós com maior grau de entrada:
| Nó | Grau de Entrada | |
|---|---|---|
| 20 | DEU | 47 |
| 15 | USA | 43 |
| 21 | FRA | 38 |
| 26 | POL | 34 |
| 8 | GBR | 33 |
| 25 | ITA | 32 |
| 38 | NLD | 30 |
| 30 | BEL | 29 |
| 7 | CHN | 29 |
| 40 | SWE | 26 |
Top 10 nós com maior grau de saída:
| Nó | Grau de Saída | |
|---|---|---|
| 7 | CHN | 147 |
| 55 | TUR | 76 |
| 20 | DEU | 64 |
| 15 | USA | 64 |
| 88 | IND | 60 |
| 38 | NLD | 47 |
| 21 | FRA | 44 |
| 8 | GBR | 39 |
| 26 | POL | 38 |
| 31 | DNK | 36 |
Grau médio de entrada de G: 8.149700598802395 Grau médio de saída de G: 8.149700598802395 Average path length de G: 2.0510921880784894
3.2.5 - Comunidades¶
# dicionários para guardar resultados por período
communities_by_period = {}
colors_by_period = {}
modularity_by_period = {}
partition_quality_by_period = {}
for period, G in networks_periodos.items():
print(f"\nPeriodo: {period}")
# aplicar Louvain diretamente no grafo direcionado com pesos
com_louvain = list(nx.community.louvain_communities(G, weight='weight', seed=42))
communities_by_period[period] = com_louvain
print(f"Número de comunidades: {len(com_louvain)}")
for i, com in enumerate(com_louvain):
print(f" - Comunidade {i+1}: {len(com)} nós")
# cor aos nós
node_color = [(0.5, 0.5, 0.5) for _ in G.nodes()]
for i, v in enumerate(G.nodes()):
for j in range(len(com_louvain)):
if v in com_louvain[j]:
node_color[i] = (0.25*j/10, 2.1*j/10, 1-2*j/10) #-> ideia encontrada no stackoverflow
colors_by_period[period] = node_color
#modularidade e qualidade
m1 = nx.community.modularity(G, communities=com_louvain, weight='weight', resolution=1)
q1 = nx.community.partition_quality(G, partition=com_louvain)
modularity_by_period[period] = m1
partition_quality_by_period[period] = q1
print(f"Índice de modularidade: {m1:.3f}")
print(f"Cobertura: {q1[0]:.3f} | Performance: {q1[1]:.3f}")
# desenhar grafo com comunidades
pos = get_geo_positions(G)
plt.figure(figsize=(20,15))
nx.draw_networkx(G, pos, node_color=node_color, with_labels=True, node_size=600,
node_shape='o', alpha=0.8, edge_color='gray', width=0.5)
plt.title(f"Comunidades Louvain - {period}")
plt.show()
# bgrafos por comunidade
f, axs = plt.subplots(nrows=math.ceil(len(com_louvain)/2), ncols=2, figsize=(22, 20))
axs = axs.flatten()
for i in range(len(com_louvain)):
G1 = G.subgraph(com_louvain[i])
node_color1 = [node_color[j] for j, v in enumerate(G.nodes()) if v in com_louvain[i]]
nx.draw_networkx_nodes(G1, pos, nodelist=com_louvain[i], node_color=node_color1,node_size=600, node_shape='o', alpha=0.8, ax=axs[i])
nx.draw_networkx(G1, pos, with_labels=True, node_size=0, edge_color='gray',width=0.5, ax=axs[i])
axs[i].set_title(f"Comunidade {i+1} - {period}")
plt.tight_layout()
plt.show()
Periodo: Pré-Pandemia Número de comunidades: 4 - Comunidade 1: 57 nós - Comunidade 2: 62 nós - Comunidade 3: 38 nós - Comunidade 4: 2 nós Índice de modularidade: 0.232 Cobertura: 0.644 | Performance: 0.679
Periodo: Durante Pandemia Número de comunidades: 4 - Comunidade 1: 12 nós - Comunidade 2: 28 nós - Comunidade 3: 79 nós - Comunidade 4: 56 nós Índice de modularidade: 0.193 Cobertura: 0.675 | Performance: 0.683
Periodo: Pós-Pandemia Número de comunidades: 4 - Comunidade 1: 19 nós - Comunidade 2: 89 nós - Comunidade 3: 56 nós - Comunidade 4: 3 nós Índice de modularidade: 0.306 Cobertura: 0.686 | Performance: 0.612
networks = {'Pré': G_pre, 'Durante': G_durante, 'Pós': G_pos}
summary_data = []
for year, G in networks.items():
stats = {
'Período': year,
'Nº Nós': G.number_of_nodes(),
'Nº Arestas': G.number_of_edges(),
'Densidade': nx.density(G),
'Grau Médio': sum(dict(G.degree()).values()) / G.number_of_nodes(),
'Grau Máximo': max(dict(G.degree()).values()),
'Componentes Fortes': nx.number_strongly_connected_components(G) if G.is_directed() else '-',
'Clustering Médio': nx.average_clustering(G.to_undirected())
}
summary_data.append(stats)
df_summary = pd.DataFrame(summary_data)
display(df_summary)
| Período | Nº Nós | Nº Arestas | Densidade | Grau Médio | Grau Máximo | Componentes Fortes | Clustering Médio | |
|---|---|---|---|---|---|---|---|---|
| 0 | Pré | 230 | 7584 | 0.143991 | 65.947826 | 291 | 101 | 0.795445 |
| 1 | Durante | 228 | 8057 | 0.155673 | 70.675439 | 311 | 102 | 0.803318 |
| 2 | Pós | 230 | 8106 | 0.153902 | 70.486957 | 290 | 109 | 0.817830 |
clustering_pre = nx.transitivity(G_pre.to_undirected())
clustering_dur = nx.transitivity(G_durante.to_undirected())
clustering_pos = nx.transitivity(G_pos.to_undirected())
print(f"Clustering Pre (Transitivity): {clustering_pre:.4f}")
print()
print(f"Clustering Dur (Transitivity): {clustering_dur:.4f}")
print()
print(f"Clustering Pos (Transitivity): {clustering_pos:.4f}")
Clustering Pre (Transitivity): 0.4942 Clustering Dur (Transitivity): 0.5065 Clustering Pos (Transitivity): 0.5068
clustering_pre = nx.transitivity(G_pre)
clustering_dur = nx.transitivity(G_durante)
clustering_pos = nx.transitivity(G_pos)
print(f"Clustering Pre (Transitivity): {clustering_pre:.4f}")
print()
print(f"Clustering Dur (Transitivity): {clustering_dur:.4f}")
print()
print(f"Clustering Pos (Transitivity): {clustering_pos:.4f}")
Clustering Pre (Transitivity): 0.3038 Clustering Dur (Transitivity): 0.3170 Clustering Pos (Transitivity): 0.3085
cluster_china_pre = nx.clustering(G_pre,'CHN')
cluster_china_dur = nx.clustering(G_durante,'CHN')
cluster_china_pos = nx.clustering(G_pos,'CHN')
print(f'China Pré: {cluster_china_pre} \nChina Durante: {cluster_china_dur} \nChina Pós: {cluster_china_pos}')
print()
cluster_usa_pre = nx.clustering(G_pre,'USA')
cluster_usa_dur = nx.clustering(G_durante,'USA')
cluster_usa_pos = nx.clustering(G_pos,'USA')
print(f'USA Pré: {cluster_usa_pre} \nUSA Durante: {cluster_usa_dur} \nUSA Pós: {cluster_usa_pos}')
print()
cluster_deu_pre = nx.clustering(G_pre,'DEU')
cluster_deu_dur = nx.clustering(G_durante,'DEU')
cluster_deu_pos = nx.clustering(G_pos,'DEU')
print(f'DEU Pré: {cluster_deu_pre} \nDEU Durante: {cluster_deu_dur} \nDEU Pós: {cluster_deu_pos}')
China Pré: 0.2528732902735562 China Durante: 0.26157460343859795 China Pós: 0.279345703125 USA Pré: 0.2925983352374735 USA Durante: 0.2930787981062204 USA Pós: 0.29561004784688993 DEU Pré: 0.2777722735491541 DEU Durante: 0.29843929365215055 DEU Pós: 0.3046244529332895
teste -> faz sentido definir bridges desta forma?¶
def analisar_bridges(G, periodo, thresh_cluster=0.3, thresh_betw=0.02):
clustering_local = nx.clustering(G)
betweenness = nx.betweenness_centrality(G)
df2 = pd.DataFrame({
'Nó': list(clustering_local.keys()),
'Clustering': list(clustering_local.values()),
'Betweenness': list(betweenness.values())
})
df2['Período'] = periodo
df2['Bridge?'] = (df2['Clustering'] < thresh_cluster) & (df2['Betweenness'] > thresh_betw)
return df2.sort_values(by='Betweenness', ascending=False)
df_bridges_pre = analisar_bridges(G_pre, 'Pré-Pandemia')
df_bridges_dur = analisar_bridges(G_durante, 'Durante a Pandemia')
df_bridges_pos = analisar_bridges(G_pos, 'Pós-Pandemia')
df_bridges_todos = pd.concat([df_bridges_pre, df_bridges_dur, df_bridges_pos])
df_bridges_todos[df_bridges_todos['Bridge?'] == True]
| Nó | Clustering | Betweenness | Período | Bridge? | |
|---|---|---|---|---|---|
| 58 | CHN | 0.252873 | 0.035045 | Pré-Pandemia | True |
| 39 | USA | 0.292598 | 0.034894 | Pré-Pandemia | True |
| 64 | DEU | 0.277772 | 0.034677 | Pré-Pandemia | True |
| 15 | FRA | 0.287857 | 0.026704 | Pré-Pandemia | True |
| 26 | NLD | 0.288453 | 0.021279 | Pré-Pandemia | True |
| 5 | CHN | 0.261575 | 0.045976 | Durante a Pandemia | True |
| 29 | USA | 0.293079 | 0.044686 | Durante a Pandemia | True |
| 11 | DEU | 0.298439 | 0.025234 | Durante a Pandemia | True |
| 31 | USA | 0.295610 | 0.045835 | Pós-Pandemia | True |
| 18 | GBR | 0.298247 | 0.026175 | Pós-Pandemia | True |
| 9 | CHN | 0.279346 | 0.021940 | Pós-Pandemia | True |
3.3 - Mapas Temporais¶
def criar_mapa_temp(networks, coords):
fig = go.Figure()
periods = list(networks.keys())
for period_idx, (period_name, G) in enumerate(networks.items()):
nodes_data = [(n, coords[n]['Longitude'], coords[n]['Latitude'], G.degree(n))
for n in G.nodes() if n in coords]
node_sizes = [min(degree * 2, 100) for _, _, _, degree in nodes_data]
# filtrar top 10% das arestas por peso
edges_with_weights = [(u, v, d['weight']) for u, v, d in G.edges(data=True)
if u in coords and v in coords]
edges_with_weights.sort(key=lambda x: x[2], reverse=True)
n_top = int(len(edges_with_weights) * 0.10)
edges_top = edges_with_weights[:n_top]
# espessura proporcional ao peso
if edges_top:
raw_weights = [w for _, _, w in edges_top]
edge_widths = [min(0.5 + w / 100_000_000, 5) for w in raw_weights]
max_width = max(edge_widths) if edge_widths else 5
# categorizar por espessura
edge_groups = []
remaining_edges = list(zip(edges_top, edge_widths))
for threshold, opacity in [(0.7, 0.7), (0.3, 0.5), (0, 0.3)]:
group = [(u, v, w, ew) for (u, v, w), ew in remaining_edges
if ew > threshold * max_width]
remaining_edges = [((u, v, w), ew) for (u, v, w), ew in remaining_edges
if ew <= threshold * max_width]
edge_groups.append((group, opacity))
else:
edge_groups = [([], 0.7), ([], 0.5), ([], 0.3)]
# adicionar 3 traços de arestas
for edges_list, opacity in edge_groups:
if edges_list:
avg_width = sum(e[3] for e in edges_list) / len(edges_list)
lons = [coord for e in edges_list for coord in
[coords[e[0]]['Longitude'], coords[e[1]]['Longitude'], None]]
lats = [coord for e in edges_list for coord in
[coords[e[0]]['Latitude'], coords[e[1]]['Latitude'], None]]
else:
avg_width = 1
lons, lats = [], []
fig.add_trace(go.Scattergeo(
lon=lons, lat=lats, mode='lines',
line=dict(width=avg_width, color=f'rgba(0, 150, 0, {opacity})'),
hoverinfo='none', showlegend=False,
visible=(period_idx == 0)
))
# adicionar nós
fig.add_trace(go.Scattergeo(
lon=[n[1] for n in nodes_data],
lat=[n[2] for n in nodes_data],
mode='markers',
marker=dict(
size=[s/5 for s in node_sizes],
color='red',
opacity=0.8
),
text=[f"{n[0]}<br>Conexões: {n[3]}" for n in nodes_data],
hovertemplate='%{text}<extra></extra>',
name=period_name,
visible=(period_idx == 0)
))
#configo do mapa e slider
fig.update_geos(
projection_type='equirectangular',
showland=True, landcolor='lightgray',
showocean=True, oceancolor='lightblue',
showcountries=True, countrycolor='white'
)
fig.update_layout(
title="Evolução das Redes de Exportação de Máscaras Cirúrgicas",
height=600,
sliders=[dict(
active=0,
currentvalue={"prefix": "Período: "},
steps=[dict(
label=period,
method="update",
args=[{"visible": [j // 4 == i for j in range(len(fig.data))]}]
) for i, period in enumerate(periods)]
)]
)
return fig
coords_df = pd.read_csv('in-nodes-All.csv')
coords = coords_df.set_index('Label')[['Longitude', 'Latitude']].to_dict('index')
fig = criar_mapa_temp(networks_periodos, coords)
fig.show()
fig.write_html("evolucao_redes_mascaras_periodos.html")
df.columns
Index(['refYear', 'refMonth', 'flowDesc', 'reporterISO', 'partnerISO', 'qty',
'primaryValue', 'unitPrice', 'Exportador', 'Importador'],
dtype='object')
def create_network(df, year, threshold=0):
G = nx.DiGraph(name=f"Rede de Exportação de Máscaras Cirúrgicas - {year}")
for _, row in df.iterrows():
if row['qty'] > threshold:
G.add_edge(
row['reporterISO'],
row['partnerISO'],
weight=row['qty']
)
return G
networks_anos = {}
for ano in range(2015, 2025):
# filtrar os dados para um ano específico
dados_ano = df_exports[df_exports['refYear'] == ano]
networks_anos[str(ano)] = create_network(dados_ano, str(ano), threshold=0)
fig = criar_mapa_temp(networks_anos, coords)
fig.show()
fig.write_html("evolucao_redes_mascaras_anual.html")
4. Portugal¶
def analisar_portugal_por_periodo(networks_periodos, coords):
portugal_stats = {}
for periodo, G in networks_periodos.items():
print(f"\n{'='*60}")
print(f"PORTUGAL - {periodo.upper()}")
print(f"{'='*60}")
#validar portugal em todos os periodos
if 'PRT' not in G.nodes():
print(f"Portugal não encontrado na rede do período {periodo}")
continue
# estatísticas básicas
grau_total = G.degree('PRT')
grau_entrada = G.in_degree('PRT')
grau_saida = G.out_degree('PRT')
print(f"Estatísticas Básicas:")
print(f" - Grau Total: {grau_total}")
print(f" - Exportações (grau saída): {grau_saida}")
print(f" - Importações (grau entrada): {grau_entrada}")
# nodos com lig dorectas
exportadores_portugal = list(G.predecessors('PRT'))
importadores_portugal = list(G.successors('PRT'))
print(f"\nParceiros Comerciais:")
print(f" - Exporta para {len(importadores_portugal)} países: {importadores_portugal}")
print(f" - Importa de {len(exportadores_portugal)} países: {exportadores_portugal}")
# centralidades
betweenness = nx.betweenness_centrality(G)['PRT']
closeness = nx.closeness_centrality(G)['PRT']
eigenvector = nx.eigenvector_centrality(G)['PRT']
pagerank = nx.pagerank(G)['PRT']
print(f"\nMedidas de Centralidade:")
print(f" - Betweenness: {betweenness:.4f}")
print(f" - Closeness: {closeness:.4f}")
print(f" - Eigenvector: {eigenvector:.4f}")
print(f" - PageRank: {pagerank:.4f}")
# ranking de Portugal em grau
all_degrees = dict(G.degree())
portugal_rank = sorted(all_degrees.values(), reverse=True).index(grau_total) + 1
total_paises = len(G.nodes())
print(f"\nPosição Relativa:")
print(f" - Ranking por grau: {portugal_rank}º de {total_paises} países")
print(f" - Percentil: {((total_paises - portugal_rank) / total_paises * 100):.1f}%")
# guardar stats
portugal_stats[periodo] = {
'grau_total': grau_total,
'grau_entrada': grau_entrada,
'grau_saida': grau_saida,
'exportadores': exportadores_portugal,
'importadores': importadores_portugal,
'ranking': portugal_rank,
'total_paises': total_paises
}
return portugal_stats
def comparar_evolucao_portugal(portugal_stats):
print(f"\n{'='*60}")
print("EVOLUÇÃO DE PORTUGAL ENTRE PERÍODOS")
print(f"{'='*60}")
periodos = list(portugal_stats.keys())
df_portugal = pd.DataFrame(portugal_stats).T
print("\nTabela Comparativa:")
display(df_portugal[['grau_total', 'grau_entrada', 'grau_saida', 'ranking', 'total_paises']])
# gráficos de evolução
fig, axes = plt.subplots(2, 2, figsize=(15, 10))
fig.suptitle("Evolução de Portugal nas Redes de Exportação de Máscaras", fontsize=16)
#grau total
axes[0, 0].plot(periodos, [portugal_stats[p]['grau_total'] for p in periodos], 'o-', color='blue')
axes[0, 0].set_title('Grau Total')
axes[0, 0].set_ylabel('Número de Conexões')
axes[0, 0].grid(True, alpha=0.3)
# export vs impor
exportacoes = [portugal_stats[p]['grau_saida'] for p in periodos]
importacoes = [portugal_stats[p]['grau_entrada'] for p in periodos]
x = range(len(periodos))
width = 0.35
axes[0, 1].bar([i - width/2 for i in x], exportacoes, width, label='Exportações', color='green')
axes[0, 1].bar([i + width/2 for i in x], importacoes, width, label='Importações', color='orange')
axes[0, 1].set_title('Exportações vs Importações')
axes[0, 1].set_ylabel('Número de Conexões')
axes[0, 1].set_xticks(x)
axes[0, 1].set_xticklabels(periodos)
axes[0, 1].legend()
axes[0, 1].grid(True, alpha=0.3)
# Rank
axes[1, 0].plot(periodos, [portugal_stats[p]['ranking'] for p in periodos], 'o-', color='red')
axes[1, 0].set_title('Posição no Ranking')
axes[1, 0].set_ylabel('Posição (menor = melhor)')
axes[1, 0].invert_yaxis() # Inverter para melhor visualização
axes[1, 0].grid(True, alpha=0.3)
# percentil
percentis = [((portugal_stats[p]['total_paises'] - portugal_stats[p]['ranking']) / portugal_stats[p]['total_paises'] * 100) for p in periodos]
axes[1, 1].plot(periodos, percentis, 'o-', color='purple')
axes[1, 1].set_title('Percentil de Conectividade')
axes[1, 1].set_ylabel('Percentil (%)')
axes[1, 1].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# análise às mudanças
print("\nAnálise de Mudanças:")
for i in range(1, len(periodos)):
periodo_anterior = periodos[i-1]
periodo_atual = periodos[i]
delta_grau = portugal_stats[periodo_atual]['grau_total'] - portugal_stats[periodo_anterior]['grau_total']
delta_ranking = portugal_stats[periodo_atual]['ranking'] - portugal_stats[periodo_anterior]['ranking']
print(f"\n{periodo_anterior} → {periodo_atual}:")
print(f" - Mudança no grau total: {delta_grau:+d}")
print(f" - Mudança no ranking: {delta_ranking:+d} posições")
# novas ligações
novos_exp = set(portugal_stats[periodo_atual]['exportadores']) - set(portugal_stats[periodo_anterior]['exportadores'])
novos_imp = set(portugal_stats[periodo_atual]['importadores']) - set(portugal_stats[periodo_anterior]['importadores'])
if novos_exp:
print(f" - Novos países que exportam para Portugal: {list(novos_exp)}")
if novos_imp:
print(f" - Novos países que importam de Portugal: {list(novos_imp)}")
# Executar análise completa
print("O Caso de Portugal")
print()
# análise estatística por período
portugal_stats = analisar_portugal_por_periodo(networks_periodos, coords)
# comparação evolutiva
comparar_evolucao_portugal(portugal_stats)
O Caso de Portugal ============================================================ PORTUGAL - PRÉ-PANDEMIA ============================================================ Estatísticas Básicas: - Grau Total: 29 - Exportações (grau saída): 15 - Importações (grau entrada): 14 Parceiros Comerciais: - Exporta para 15 países: ['AGO', 'CHE', 'CZE', 'DEU', 'DNK', 'ESP', 'FRA', 'GBR', 'IRL', 'ITA', 'MAR', 'NLD', 'NOR', 'SWE', 'TUN'] - Importa de 14 países: ['BEL', 'CHN', 'DEU', 'ESP', 'FRA', 'GBR', 'HKG', 'IND', 'ITA', 'MAR', 'NLD', 'ROU', 'TUR', 'VNM'] Medidas de Centralidade: - Betweenness: 0.0010 - Closeness: 0.1977 - Eigenvector: 0.1195 - PageRank: 0.0072 Posição Relativa: - Ranking por grau: 23º de 159 países - Percentil: 85.5% ============================================================ PORTUGAL - DURANTE PANDEMIA ============================================================ Estatísticas Básicas: - Grau Total: 36 - Exportações (grau saída): 19 - Importações (grau entrada): 17 Parceiros Comerciais: - Exporta para 19 países: ['AGO', 'AUT', 'BEL', 'CHE', 'CPV', 'CZE', 'DEU', 'DNK', 'ESP', 'FRA', 'GBR', 'IRL', 'ITA', 'NLD', 'POL', 'ROU', 'SVK', 'SWE', 'USA'] - Importa de 17 países: ['BEL', 'CHE', 'CHN', 'CZE', 'DEU', 'ESP', 'FRA', 'GBR', 'HKG', 'IND', 'ITA', 'MAR', 'NLD', 'ROU', 'SVN', 'TUR', 'USA'] Medidas de Centralidade: - Betweenness: 0.0021 - Closeness: 0.2391 - Eigenvector: 0.1393 - PageRank: 0.0091 Posição Relativa: - Ranking por grau: 22º de 175 países - Percentil: 87.4% ============================================================ PORTUGAL - PÓS-PANDEMIA ============================================================ Estatísticas Básicas: - Grau Total: 40 - Exportações (grau saída): 21 - Importações (grau entrada): 19 Parceiros Comerciais: - Exporta para 21 países: ['AGO', 'BEL', 'BGR', 'CHE', 'CZE', 'DEU', 'DNK', 'DZA', 'ESP', 'FRA', 'GBR', 'IRL', 'ITA', 'MAR', 'NLD', 'NOR', 'POL', 'ROU', 'SVK', 'SWE', 'USA'] - Importa de 19 países: ['BEL', 'CHN', 'CZE', 'DEU', 'DNK', 'ESP', 'FRA', 'GBR', 'GRC', 'ITA', 'MAR', 'NLD', 'POL', 'ROU', 'SVN', 'SWE', 'TUR', 'UKR', 'USA'] Medidas de Centralidade: - Betweenness: 0.0020 - Closeness: 0.2396 - Eigenvector: 0.1499 - PageRank: 0.0091 Posição Relativa: - Ranking por grau: 21º de 167 países - Percentil: 87.4% ============================================================ EVOLUÇÃO DE PORTUGAL ENTRE PERÍODOS ============================================================ Tabela Comparativa:
| grau_total | grau_entrada | grau_saida | ranking | total_paises | |
|---|---|---|---|---|---|
| Pré-Pandemia | 29 | 14 | 15 | 23 | 159 |
| Durante Pandemia | 36 | 17 | 19 | 22 | 175 |
| Pós-Pandemia | 40 | 19 | 21 | 21 | 167 |
Análise de Mudanças: Pré-Pandemia → Durante Pandemia: - Mudança no grau total: +7 - Mudança no ranking: -1 posições - Novos países que exportam para Portugal: ['USA', 'SVN', 'CZE', 'CHE'] - Novos países que importam de Portugal: ['SVK', 'CPV', 'AUT', 'BEL', 'USA', 'POL', 'ROU'] Durante Pandemia → Pós-Pandemia: - Mudança no grau total: +4 - Mudança no ranking: -1 posições - Novos países que exportam para Portugal: ['GRC', 'UKR', 'DNK', 'SWE', 'POL'] - Novos países que importam de Portugal: ['DZA', 'MAR', 'BGR', 'NOR']